package com.bitantenna.streams.audio;

import java.io.ByteArrayOutputStream;

import org.xiph.libogg.ogg_packet;
import org.xiph.libogg.ogg_page;
import org.xiph.libogg.ogg_stream_state;
import org.xiph.libvorbis.vorbis_block;
import org.xiph.libvorbis.vorbis_comment;
import org.xiph.libvorbis.vorbis_dsp_state;
import org.xiph.libvorbis.vorbis_info;
import org.xiph.libvorbis.vorbisenc;

import com.bitantenna.messages.MessageFrame;
import com.bitantenna.messages.MessageFrame.FrameType;
import com.bitantenna.streams.StreamInput;
import com.bitantenna.streams.StreamOutput;



public class OggFromPcmInput implements StreamInput {
	vorbis_info			info;	// struct that stores all the static vorbis bitstream settings
	vorbisenc 			encoder;
	vorbis_comment		comment;	// struct that stores all the user comments
	vorbis_dsp_state		dspState;	// central working state for the packet->PCM decoder
	vorbis_block			block;	// local working space for packet->PCM decode
	int READ = 1024;
	//byte[] readbuffer = new byte[READ*4+44];	
	int pageCount = 0;
	int blockCount = 0;
	ogg_stream_state 	streamState;	// take physical pages, weld into a logical stream of packets

	ogg_page				page;	// one Ogg bitstream page.  Vorbis packets are inside
	ogg_packet			oggPacket;	// one raw packet of data for decode

	PcmRecordInput pcmInput;
	boolean eos =false;
	
	public OggFromPcmInput(PcmRecordInput pcmInput) {
		this.pcmInput=pcmInput;			

		info = new vorbis_info();

		encoder = new vorbisenc();

		if ( !encoder.vorbis_encode_init_vbr( info, 2, 44100, .3f ) ) {
			System.out.println( "Failed to Initialize vorbisenc" );
			return;
		}
		
		comment = new vorbis_comment();
		comment.vorbis_comment_add_tag( "ENCODER", "Java Vorbis Encoder" );

		dspState = new vorbis_dsp_state();
		
		if ( !dspState.vorbis_analysis_init( info ) ) {
			System.out.println( "Failed to Initialize vorbis_dsp_state" );
			return;
		}
		
		block = new vorbis_block( dspState );

		java.util.Random generator = new java.util.Random();  // need to randomize seed
		streamState = new ogg_stream_state( generator.nextInt(256) );
		
		System.out.print( "Writing header." );
		ogg_packet header = new ogg_packet();
		ogg_packet header_comm = new ogg_packet();
		ogg_packet header_code = new ogg_packet();
		
		dspState.vorbis_analysis_headerout( comment, header, header_comm, header_code );
		
		streamState.ogg_stream_packetin( header); // automatically placed in its own page
		streamState.ogg_stream_packetin( header_comm );
		streamState.ogg_stream_packetin( header_code );
		
		page = new ogg_page();
		oggPacket = new ogg_packet();		

	}
	@Override
	public boolean isCompleted() {
		return pcmInput.isCompleted();
	}

	@Override
	public MessageFrame readHeader() {
		ByteArrayOutputStream oggStream=new ByteArrayOutputStream();
		
		while( !eos ) {
			
			if ( !streamState.ogg_stream_flush( page ) )
				break;
			
			oggStream.write( page.header, 0, page.header_len );
			oggStream.write( page.body, 0, page.body_len );
			System.out.print( "." );
		}
		System.out.print(  "Done.\n" );
		
		return new MessageFrame(FrameType.header).payloadBytes(oggStream.toByteArray());
	}

	@Override
	public MessageFrame readSample() {
		System.out.println("readSample");
		byte[] pcmBuffer=pcmInput.readSample().payloadBytes();
		ByteArrayOutputStream oggStream=new ByteArrayOutputStream(pcmBuffer.length);
		
			
			int i;			
			int pcmLength = pcmBuffer.length;
			
			int break_count = 0;
			
			if ( pcmLength==0 ) { // end of file.  this can be done implicitly in the mainline, but it's easier to see here in non-clever fashion. Tell the library we're at end of stream so that it can handle, the last frame and mark end of stream in the output properly
				
				dspState.vorbis_analysis_wrote( 0 );
				
			} else {
				
				// data to encode
				
				// expose the buffer to submit data
				float[][] buffer = dspState.vorbis_analysis_buffer( pcmBuffer.length );
				
				// uninterleave samples
				for ( i=0; i < pcmLength/4; i++ ) {
					buffer[0][dspState.pcm_current + i] = ( (pcmBuffer[i*4+1]<<8) | (0x00ff&(int)pcmBuffer[i*4]) ) / 32768.f;
					buffer[1][dspState.pcm_current + i] = ( (pcmBuffer[i*4+3]<<8) | (0x00ff&(int)pcmBuffer[i*4+2]) ) / 32768.f;
				}
				
				// tell the library how much we actually submitted
				dspState.vorbis_analysis_wrote( i );
			}

			// vorbis does some data preanalysis, then divvies up blocks for more involved 
			// (potentially parallel) processing.  Get a single block for encoding now

			while ( block.vorbis_analysis_blockout( dspState ) ) {

				// analysis, assume we want to use bitrate management
				
				block.vorbis_analysis( null );
				block.vorbis_bitrate_addblock();
				
				while ( dspState.vorbis_bitrate_flushpacket( oggPacket ) ) {
					
					// weld the packet into the bitstream
					streamState.ogg_stream_packetin( oggPacket );
					
					// write out pages (if any)
					while ( !eos ) {
						
						if ( !streamState.ogg_stream_pageout( page ) ) {
							break_count++;
							break;
						}
						
						
						oggStream.write( page.header, 0, page.header_len );
						oggStream.write( page.body, 0, page.body_len );
												
						if ( page.ogg_page_eos() > 0 ) // this could be set above, but for illustrative purposes, I do it here (to show that vorbis does know where the stream ends)
							eos = true;
					}
				}
			}
					
		return new MessageFrame(FrameType.sample).payloadBytes(oggStream.toByteArray());
	}

	@Override
	public float sampleDuration() {
		return pcmInput.sampleDuration();
	}
	
	public static void main(String[] args) throws Exception {
		StreamInput in=new OggFromPcmInput(new PcmRecordInput());
		StreamOutput out=new OggToPcmOutput2(null);
		out.writeHeader(in.readHeader());
		
		while (!in.isCompleted()) {
			out.writeSample(in.readSample());
		}
	}
	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}

}
